1 /** 2 * Copyright (c) 2019 Laurent CARON. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Laurent CARON (laurent.caron at gmail dot com) - initial API and implementation (bug 542777) 13 */ 14 package org.eclipse.swt.custom; 15 16 import org.eclipse.swt.*; 17 import org.eclipse.swt.graphics.*; 18 import org.eclipse.swt.widgets.*; 19 20 /** 21 * This class add the following behaviour to <code>StyledText</code> widgets: 22 * <p> 23 * When the user clicks on the wheel, a circle with arrows appears. When the user moves the mouse, 24 * the StyledText content is scrolled (on the right or on the left for horizontal movements, up or down for vertical movements). 25 * </p> 26 * 27 * @since 3.110 28 */ 29 class MouseNavigator { 30 private final StyledText parent; 31 boolean navigationActivated = false; 32 private GC gc; 33 private static final int CIRCLE_RADIUS = 15; 34 private static final int CENTRAL_POINT_RADIUS = 2; 35 private Point originalMouseLocation; 36 private final Listener mouseDownListener, mouseUpListener, paintListener, mouseMoveListener, focusOutListener; 37 private boolean hasHBar, hasVBar; 38 private Cursor previousCursor; 39 MouseNavigator(final StyledText styledText)40 MouseNavigator(final StyledText styledText) { 41 if (styledText == null) { 42 SWT.error(SWT.ERROR_NULL_ARGUMENT); 43 } 44 if (styledText.isDisposed()) { 45 SWT.error(SWT.ERROR_WIDGET_DISPOSED); 46 } 47 parent = styledText; 48 49 mouseDownListener = (event) -> { 50 onMouseDown(event); 51 }; 52 parent.addListener(SWT.MouseDown, mouseDownListener); 53 54 mouseUpListener = (event) -> { 55 onMouseUp(event); 56 }; 57 parent.addListener(SWT.MouseUp, mouseUpListener); 58 59 paintListener = (event) -> { 60 onPaint(event); 61 }; 62 parent.addListener(SWT.Paint, paintListener); 63 64 mouseMoveListener = (event) -> { 65 onMouseMove(event); 66 }; 67 parent.addListener(SWT.MouseMove, mouseMoveListener); 68 69 focusOutListener = (event) -> { 70 onFocusOut(event); 71 }; 72 parent.addListener(SWT.FocusOut, focusOutListener); 73 } 74 onMouseDown(Event e)75 void onMouseDown(Event e) { 76 if ((e.button != 2) || navigationActivated) { 77 return; 78 } 79 80 if (!parent.isVisible() || !parent.getEnabled() || parent.middleClickPressed) { 81 return; 82 } 83 84 // Widget has no bar or bars are not enabled 85 initBarState(); 86 87 if (!hasHBar && !hasVBar) { 88 return; 89 } 90 91 navigationActivated = true; 92 previousCursor = parent.getCursor(); 93 parent.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_ARROW)); 94 originalMouseLocation = getMouseLocation(); 95 parent.redraw(); 96 } 97 initBarState()98 private void initBarState() { 99 hasHBar = computeHasHorizontalBar(); 100 hasVBar = computeHasVerticalBar(); 101 } 102 computeHasHorizontalBar()103 private boolean computeHasHorizontalBar() { 104 final ScrollBar horizontalBar = parent.getHorizontalBar(); 105 final boolean hasHorizontalBar = horizontalBar != null && horizontalBar.isVisible(); 106 final boolean exceedHorizontalSpace = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT).x > parent.getSize().x; 107 return hasHorizontalBar && exceedHorizontalSpace; 108 } 109 computeHasVerticalBar()110 private boolean computeHasVerticalBar() { 111 final ScrollBar verticalBar = parent.getVerticalBar(); 112 final boolean hasVerticalBar = verticalBar != null && verticalBar.isEnabled(); 113 final boolean exceedVerticalSpace = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT).y > parent.getSize().y; 114 return hasVerticalBar && exceedVerticalSpace; 115 } 116 onMouseUp(Event e)117 private void onMouseUp(Event e) { 118 if ((computeDist() < CIRCLE_RADIUS) && (computeDist() >= 0)) { 119 return; 120 } 121 deactivate(); 122 } 123 computeDist()124 public int computeDist() { 125 if (originalMouseLocation == null) { 126 return -1; 127 } 128 final Point mouseLocation = getMouseLocation(); 129 final int deltaX = originalMouseLocation.x - mouseLocation.x; 130 final int deltaY = originalMouseLocation.y - mouseLocation.y; 131 final int dist = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY); 132 return dist; 133 } 134 deactivate()135 private void deactivate() { 136 parent.setCursor(previousCursor); 137 navigationActivated = false; 138 originalMouseLocation = null; 139 parent.redraw(); 140 } 141 onFocusOut(Event e)142 private void onFocusOut(Event e) { 143 deactivate(); 144 } 145 onMouseMove(Event e)146 private void onMouseMove(Event e) { 147 if (!navigationActivated) { 148 return; 149 } 150 151 final Point mouseLocation = getMouseLocation(); 152 final int deltaX = originalMouseLocation.x - mouseLocation.x; 153 final int deltaY = originalMouseLocation.y - mouseLocation.y; 154 final int dist = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY); 155 if (dist < CIRCLE_RADIUS) { 156 return; 157 } 158 159 parent.setRedraw(false); 160 if (hasHBar) { 161 final ScrollBar bar = parent.getHorizontalBar(); 162 bar.setSelection((int) (bar.getSelection() - deltaX * .1)); 163 fireSelectionEvent(e, bar); 164 } 165 166 if (hasVBar) { 167 final ScrollBar bar = parent.getVerticalBar(); 168 bar.setSelection((int) (bar.getSelection() - deltaY * .1)); 169 fireSelectionEvent(e, bar); 170 } 171 parent.setRedraw(true); 172 parent.redraw(); 173 } 174 fireSelectionEvent(final Event e, final ScrollBar bar)175 private void fireSelectionEvent(final Event e, final ScrollBar bar) { 176 final Event event = new Event(); 177 event.widget = bar; 178 event.display = parent.getDisplay(); 179 event.type = SWT.Selection; 180 event.time = e.time; 181 182 for (final Listener selectionListener : bar.getListeners(SWT.Selection)) { 183 selectionListener.handleEvent(event); 184 } 185 } 186 getMouseLocation()187 private Point getMouseLocation() { 188 final Point cursorLocation = Display.getCurrent().getCursorLocation(); 189 final Point relativeCursorLocation = parent.toControl(cursorLocation); 190 return relativeCursorLocation; 191 } 192 onPaint(final Event e)193 private void onPaint(final Event e) { 194 if (!navigationActivated) { 195 return; 196 } 197 198 final Rectangle rect = parent.getClientArea(); 199 if (rect.width == 0 || rect.height == 0) { 200 return; 201 } 202 gc = e.gc; 203 gc.setAntialias(SWT.ON); 204 gc.setAdvanced(true); 205 206 final Color oldForegroundColor = gc.getForeground(); 207 final Color oldBackgroundColor = gc.getBackground(); 208 gc.setBackground(parent.getForeground()); 209 210 drawCircle(); 211 drawCentralPoint(); 212 213 drawArrows(); 214 215 gc.setForeground(oldForegroundColor); 216 gc.setBackground(oldBackgroundColor); 217 } 218 drawCircle()219 private void drawCircle() { 220 gc.setBackground(parent.getBackground()); 221 gc.setForeground(parent.getForeground()); 222 gc.setAlpha(200); 223 gc.fillOval(originalMouseLocation.x - CIRCLE_RADIUS, originalMouseLocation.y - CIRCLE_RADIUS, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2); 224 gc.setBackground(parent.getForeground()); 225 gc.setAlpha(255); 226 gc.drawOval(originalMouseLocation.x - CIRCLE_RADIUS, originalMouseLocation.y - CIRCLE_RADIUS, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2); 227 } 228 drawCentralPoint()229 private void drawCentralPoint() { 230 gc.fillOval(originalMouseLocation.x - CENTRAL_POINT_RADIUS, originalMouseLocation.y - CENTRAL_POINT_RADIUS, CENTRAL_POINT_RADIUS * 2, CENTRAL_POINT_RADIUS * 2); 231 } 232 drawArrows()233 private void drawArrows() { 234 gc.setLineWidth(2); 235 if (hasHBar) { 236 drawHorizontalArrows(); 237 } 238 if (hasVBar) { 239 drawVerticalArrows(); 240 } 241 } 242 drawHorizontalArrows()243 private void drawHorizontalArrows() { 244 final int[] points = new int[6]; 245 // Left 246 points[0] = originalMouseLocation.x - 6; 247 points[1] = originalMouseLocation.y + 3; 248 points[2] = originalMouseLocation.x - 9; 249 points[3] = originalMouseLocation.y; 250 points[4] = originalMouseLocation.x - 6; 251 points[5] = originalMouseLocation.y - 3; 252 gc.drawPolyline(points); 253 254 // Right 255 points[0] = originalMouseLocation.x + 7; 256 points[1] = originalMouseLocation.y + 3; 257 points[2] = originalMouseLocation.x + 10; 258 points[3] = originalMouseLocation.y; 259 points[4] = originalMouseLocation.x + 7; 260 points[5] = originalMouseLocation.y - 3; 261 gc.drawPolyline(points); 262 } 263 drawVerticalArrows()264 private void drawVerticalArrows() { 265 final int[] points = new int[6]; 266 // Upper 267 points[0] = originalMouseLocation.x - 3; 268 points[1] = originalMouseLocation.y - 6; 269 points[2] = originalMouseLocation.x; 270 points[3] = originalMouseLocation.y - 10; 271 points[4] = originalMouseLocation.x + 3; 272 points[5] = originalMouseLocation.y - 6; 273 gc.drawPolyline(points); 274 275 // Lower 276 points[0] = originalMouseLocation.x - 3; 277 points[1] = originalMouseLocation.y + 7; 278 points[2] = originalMouseLocation.x; 279 points[3] = originalMouseLocation.y + 11; 280 points[4] = originalMouseLocation.x + 3; 281 points[5] = originalMouseLocation.y + 7; 282 gc.drawPolyline(points); 283 284 } 285 dispose()286 void dispose() { 287 if (parent.isDisposed()) { 288 return; 289 } 290 parent.removeListener(SWT.MouseDown, mouseDownListener); 291 parent.removeListener(SWT.MouseUp, mouseUpListener); 292 parent.removeListener(SWT.Paint, paintListener); 293 parent.removeListener(SWT.MouseMove, mouseMoveListener); 294 parent.removeListener(SWT.MouseExit, focusOutListener); 295 } 296 } 297